package clustering.streamming;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import clustering.K_MeansClustering;

import preprocess.XMLDoc;
import similarity.Similarity;
import ui.clustering.FastMap;
import ui.clustering.VisClusteringInfo;
import utils.CategoriesInfo;
import utils.ResourceCritical;
import utils.ThreadCritical;

@SuppressWarnings("unused")
public class StreammingKMedianClustering implements Runnable
{
    private K_MeansClustering                 kmeansClu;
    private KMediansClustering                kmediansClu;
    private Hashtable<Integer, List<Cluster>> levelsCluster;
    private ThreadCritical                    threadLock;
    private CategoriesInfo                    result;
    private boolean                           isHalt;
    private int                               m;
    private int                               k;
    private int                               n;
    private Similarity                        similarity;
    private ResourceCritical<XMLDoc>          resource;

    public StreammingKMedianClustering(int blockSize, int clusterSize,
            int levelTimes, Similarity similarity,
            ResourceCritical<XMLDoc> resource)
    {
        isHalt = false;
        m = blockSize;
        k = clusterSize;
        n = levelTimes;
        this.similarity = similarity;
        kmeansClu = new K_MeansClustering(this.similarity);
        kmediansClu = new KMediansClustering(this.similarity);
        threadLock = new ThreadCritical();
        levelsCluster = new Hashtable<Integer, List<Cluster>>();
        this.resource = resource;
    }

    @Override
    public void run()
    {
        System.out.println("thread " + this + " start...");
        while (!isHalt())
        {
            try
            {
                if (!levelsCluster.containsKey(0))
                    levelsCluster.put(0, new ArrayList<Cluster>());
                LinkedList<Cluster> tmpList = new LinkedList<Cluster>();
                for (int i = 0; i < m; i++)
                {
                    tmpList.add(Cluster.createCluster(resource.pop()));
                    // System.out.println("add document:" +
                    // tmpList.get(i).getDocID());
                }
                levelCluste(tmpList, tmpList.get(0).getLevel());
                // List<List<XMLDoc>> cluResult = kmeansClu
                // .cluster(tmpList, n * k);
                // List<Cluster> result = new LinkedList<Cluster>();
                // for (List<XMLDoc> list : cluResult)
                // {
                // Cluster clu = Cluster.createCluster(list, kmeansClu
                // .getCenter(list));
                // result.add(clu);
                // }
                // levelCluste(result, result.get(0).getLevel());
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    private void levelCluste(List<Cluster> clusters, int level)
    {
        System.out.println("levelCluste: [level " + level + "]");
        if (!levelsCluster.containsKey(level))
        {
            System.out.println("new level: [level " + level + "]");
            levelsCluster.put(level, new LinkedList<Cluster>());
        }
        List<Cluster> container = levelsCluster.get(level);
        container.addAll(clusters);
        System.out.println("level context: [" + level + "]--" + container);
        System.out.println("level size:" + container.size());
        if (container.size() >= m)
        {
            List<Cluster> list = new LinkedList<Cluster>();
            for (int i = 0; i < m; i++)
                list.add(container.remove(0));
            List<List<Cluster>> cluResult = kmediansClu.cluster(list, n * k);
            List<Cluster> result = new LinkedList<Cluster>();
            for (List<Cluster> l : cluResult)
            {
                result.add(Cluster.combine(l, similarity));
            }
            levelCluste(result, level + 1);
        }
    }

    public VisClusteringInfo getCurrentClusteringResultAsVisClusteringInfo()
            throws Exception
    {
        VisClusteringInfo result = new VisClusteringInfo();
        List<Cluster> cluResult = getCurrentClusteringResult();
        CategoriesInfo info = toCategoriesInfo(cluResult);
        result.setCategoriesInfo(info);
        int size = cluResult.size();
        double[][] disMatrix = new double[size][size];
        double maxDis = Double.MIN_VALUE, minDis = Double.MAX_VALUE;
        for (int i = 0; i < size; i++)
        {
            XMLDoc centeri = cluResult.get(i).getCenter();
            for (int j = i + 1; j < size; j++)
            {
                XMLDoc centerj = cluResult.get(j).getCenter();
                double sim = similarity.calcSimilarity(centeri, centerj);
                double distance = Math.max(1 - sim, 0);
                disMatrix[i][j] = disMatrix[j][i] = distance;
                maxDis = Math.max(maxDis, distance);
                minDis = Math.min(minDis, distance);
            }
        }
        for (int i = 0; i < size; i++)
        {
            for (int j = i + 1; j < size; j++)
            {
                double dis = disMatrix[i][j];
                double scale = (dis + 1E-10 - minDis) / (maxDis - minDis);
                disMatrix[i][j] = disMatrix[j][i] = scale;
            }
        }
        FastMap map = new FastMap();
        try
        {
            ArrayList<double[]> mapResult = map.map(disMatrix, 2);
            HashMap<String, double[]> coordinateInfo = new HashMap<String, double[]>();
            for (int i = 0; i < size; i++)
            {
                coordinateInfo.put(cluResult.get(i).getCenter().getDocID(),
                        mapResult.get(i));
            }
            result.setCoordinateInfo(coordinateInfo);
        }
        catch (Exception e)
        {
            // e.printStackTrace();
            // System.exit(0);
        }
        return result;
    }

    private List<Cluster> getCurrentClusteringResult()
    {
        List<Cluster> clusters = new LinkedList<Cluster>();
        for (List<Cluster> list : levelsCluster.values())
        {
            clusters.addAll(list);
        }
        // k 原来为 n*k 不知为何
        List<List<Cluster>> cluResult = kmediansClu.cluster(clusters, k);
        List<Cluster> result = new LinkedList<Cluster>();
        for (List<Cluster> li : cluResult)
        {
            result.add(Cluster.combine(li, similarity));
        }
        return result;
    }

    /**
     * @return the isHalt
     */
    private boolean isHalt()
    {
        threadLock.getLock();
        boolean result = isHalt;
        threadLock.releaseLock();
        return result;
    }

    /**
     * terminate the current thread
     */
    public void halt()
    {
        threadLock.getLock();
        isHalt = true;
        threadLock.releaseLock();
    }

    private CategoriesInfo toCategoriesInfo(List<Cluster> clusters)
    {
        HashMap<String, HashSet<String>> map = new HashMap<String, HashSet<String>>();
        for (int i = 0; i < clusters.size(); i++)
        {
            HashSet<String> set = new HashSet<String>();
            for (String id : clusters.get(i).getContext())
            {
                set.add(id);
            }
            map.put(clusters.get(i).getCenter().getDocID(), set);
        }
        CategoriesInfo result = CategoriesInfo.createCategoriesInfo(map);
        return result;
    }
}
